package titt.server.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import titt.head.HeadFactory;
import titt.head.Content;
import titt.head.ContentHead;
import titt.head.response.ResponseHead;
import titt.io.IOFactory;
import titt.io.socket.TITTSocket;
import titt.key.TITTKey;
import titt.server.os.ObjectServer;
import titt.server.service.listener.ServiceListener;
import titt.server.service.listener.SuccessListener;
import titt.server.service.listener.UpdateSuccess;
import titt.service.ServicePackage;
import titt.util.io.pipe.PipeListener;
import titt.util.io.pipe.StreamPiper;

public class ServiceImpl implements Service, UpdateSuccess {

	private final int ERROR_CODE = 418;

	private final StreamPiper piper = IOFactory.createStreamPiper();

	private final TITTKey key;
	private final String id;
	private final String[][] iface;

	private boolean stopped = false;
	private long lastSuccess = -1;
	
	private final ArrayList<ServicePackage> workRequests = new ArrayList<ServicePackage>();
	private final Map<String, ServicePackage> total = new HashMap<String, ServicePackage>();
	private final ArrayList<TITTSocket> dependicies = new ArrayList<TITTSocket>();
	private final ArrayList<ServiceListener> listeners = new ArrayList<ServiceListener>();
	
	private ObjectServer os;
	
	public ServiceImpl(ObjectServer os, TITTKey key, String id, String[][] iface) {
		this.key = key;
		this.id = id;
		this.iface = iface;
		this.os = os;
	}
	
	public String[][] getRegistrationInterface() {
		return iface;
	}

	public synchronized void addDependency(TITTSocket ts) throws Exception {
		if(stopped) {
			ts.getHeadWriter().writeHead(new ResponseHead(404, HeadFactory.EMPTY));
			ts.close();
			return;
		}

		dependicies.add(ts);
	}

	public synchronized long lastSuccess() {
		return lastSuccess;
	}
	
	public synchronized void removeDependency(TITTSocket ts) throws Exception {
		dependicies.remove(ts);
	}
	
	public synchronized void deposit(ServicePackage pack) {

		if(stopped) {
			respond(pack, 404);
			this.notifyAll();
			return;
		}
		workRequests.add(pack);
		total.put(pack.getPackageID(), pack);

		this.notifyAll();
		
	}
	
	public synchronized void waitForCompletion(int maxSec) throws Exception {
		long max = System.currentTimeMillis() + maxSec * 1000;
		while(total.size() > 0 && System.currentTimeMillis() < max)
			wait(Math.max(1,max - System.currentTimeMillis()));
	}
	
	public synchronized ServicePackage withdraw() throws Exception {

		while(workRequests.size() == 0 && !stopped)
			this.wait();

		if(stopped) return null;

		return workRequests.remove(0);

	}
	
	public void sync(ServicePackage pack) {

		ServicePackage syncPackage = null;

		synchronized (this) {

			if(!total.containsKey(pack.getPackageID())) {
				respond(pack, 404);
				return;
			}

			syncPackage = total.remove(pack.getPackageID());

		}

		try {

			ContentHead content = new Content(pack.getHead());

			TITTSocket from = pack.getTITTSocket(), to = syncPackage.getTITTSocket(); 

			to.getHeadWriter().writeHead(os.addResponseHeaders(content));

			PipeListener al = pack.getAssociatedListener(), sl = new SuccessListener(this);
			
			if(content.containsContentLength())
				piper.pipe(from.getInputStream(), to.getOutputStream(), content.getContentLength(), IOFactory.createListener(al == null? sl : al, sl));
			
			respond(pack, 200);
			
			onSuccess(System.currentTimeMillis());

		}
		catch(Exception e) {
			respond(pack, ERROR_CODE);
			respond(syncPackage, ERROR_CODE);

		}

		//housekeeping
		try {
			if(!syncPackage.getTITTSocket().isClosed())
				syncPackage.getTITTSocket().close();

			if(!pack.getTITTSocket().isClosed())
				pack.getTITTSocket().close();
		}
		catch(Exception e) {

		}
		synchronized (this) {	
			notifyAll();
		}
	}
	
	public synchronized void attachServiceListener(ServiceListener sl) {
		if(stopped)
			sl.onServiceStop();
		
		listeners.add(sl);
	}
	
	public synchronized void removeServiceListener(ServiceListener sl) {
		listeners.remove(sl);
	}

	public synchronized void stop() throws Exception {

		stopped = true;

		while(total.size() > 0) 
			respond(total.remove(total.keySet().iterator().next()), 404);

		while(workRequests.size() > 0)
			respond(workRequests.remove(0), 404);

		while(dependicies.size() > 0) {
			TITTSocket d = dependicies.remove(0);
			if(!d.isClosed())
				d.close();
		}
		
		for(ServiceListener sl : listeners)
			sl.onServiceStop();
		
		this.notifyAll();
	}

	private void respond(ServicePackage pack, int status) {
		try {

			if(!pack.getTITTSocket().isClosed()) {
				pack.getTITTSocket().getHeadWriter().writeHead(new ResponseHead(status, os.addResponseHeaders(HeadFactory.EMPTY)));
				pack.getTITTSocket().close();
			}

		}
		catch(Exception e) {
			try {
				if(!pack.getTITTSocket().isClosed()) {
					pack.getTITTSocket().close();
				}
			}
			catch(Exception ex) {
			}
		}
	}

	public String getID() {
		return id;
	}

	public TITTKey getKey() {
		return key;
	}

	public int compareTo(Service o) {
		return key.compareTo(o.getKey());
	}

	@Override
	public String toString() {
		return getID() + " @" + key.asKeyString();
	}

	@Override
	public synchronized void onSuccess(long time) {
		lastSuccess = time;		
	}
}
